﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace BlinkyDevice
{
    /// <summary>
    /// Class to wrap USB API calls
    /// </summary>
    public class UsbImports
    {
        #region Constants
        // Windows message sent when a device is inserted or removed
        public const int WM_DEVICECHANGE = 0x0219;
        // WParam for above : A device was inserted
        public const int DEVICE_ARRIVAL = 0x8000;
        //WParam for above : A device was removed
        public const int DEVICE_REMOVECOMPLETE = 0x8004;
        // Used in SetupDiClassDevs to get devices present in the system
        protected const int DIGCF_PRESENT = 0x02;
        // Used in SetupDiClassDevs to get device interface details
        protected const int DIGCF_DEVICEINTERFACE = 0x10;
        // Used when registering for device insert/remove messages : specifies the type of device
        protected const int DEVTYP_DEVICEINTERFACE = 0x05;
        // Used when registering for device insert/remove messages : we're giving the API call a window handle
        protected const int DEVICE_NOTIFY_WINDOW_HANDLE = 0;
        // Purges Win32 transmit buffer by aborting the current transmission.
        protected const uint PURGE_TXABORT = 0x01;
        // Purges Win32 receive buffer by aborting the current receive.
        protected const uint PURGE_RXABORT = 0x02;
        // Purges Win32 transmit buffer by clearing it.
        protected const uint PURGE_TXCLEAR = 0x04;
        // Purges Win32 receive buffer by clearing it.
        protected const uint PURGE_RXCLEAR = 0x08;
        // CreateFile : Open file for read 
        protected const uint GENERIC_READ = 0x80000000;
        // CreateFile : Open file for write<
        protected const uint GENERIC_WRITE = 0x40000000;
        // CreateFile : Open handle for overlapped operations 
        protected const uint FILE_FLAG_OVERLAPPED = 0x40000000;
        // CreateFile : Resource to be "created" must exist 
        protected const uint OPEN_EXISTING = 3;
        // ReadFile/WriteFile : Overlapped operation is incomplete.
        protected const uint ERROR_IO_PENDING = 997;
        // Infinite timeout
        protected const uint INFINITE = 0xFFFFFFFF;
        // Simple representation of a null handle : a closed stream will get this handle. Note it is public for comparison by higher level classes.
        public static IntPtr NullHandle = IntPtr.Zero;
        // Simple representation of the handle returned when CreateFile fails.
        protected static IntPtr InvalidHandleValue = new IntPtr(-1);
        #endregion

        #region Structures
        /// <summary>
        /// Structure used by Windows to keep state of pending operations.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        protected struct Overlapped
        {
            public uint Internal;
            public uint InternalHigh;
            public uint Offset;
            public uint OffsetHigh;
            public IntPtr Event;
        }

        /// <summary>
        /// Provides details about a USB device
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        protected struct DeviceInterfaceData
        {
            public int Size;
            public Guid InterfaceClassGuid;
            public int Flags;
            public int Reserved;
        }

        /// <summary>
        /// Provides capabilities about a USB device
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        protected struct HidCaps
        {
            public short Usage;
            public short UsagePage;
            public short InputReportByteLength;
            public short OutputReportByteLength;
            public short FeatureReportByteLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
            public short[] Reserved;
            public short NumberLinkCollectionNodes;
            public short NumberInputButtonCaps;
            public short NumberInputValueCaps;
            public short NumberInputDataIndices;
            public short NumberOutputButtonCaps;
            public short NumberOutputValueCaps;
            public short NumberOutputDataIndices;
            public short NumberFeatureButtonCaps;
            public short NumberFeatureValueCaps;
            public short NumberFeatureDataIndices;
        }

        /// <summary>
        /// Access to the device path
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct DeviceInterfaceDetailData
        {
            public int Size;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string DevicePath;
        }

        /// <summary>
        /// Receives messages about devices added or removed from system
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
        public class DeviceBroadcastInterface
        {
            public int Size;
            public int DeviceType;
            public int Reserved;
            public Guid ClassGuid;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string Name;
        }
        #endregion

        #region DLL Imports
        /// <summary>
        /// Retrieves the GUID
        /// </summary>
        /// <param name="gHid">At out parameter to store the GUID</param>
        [DllImport("hid.dll", SetLastError = true)]
        protected static extern void HidD_GetHidGuid(out Guid gHid);

        /// <summary>
        /// Retrieves the device capabilities from preparsed data
        /// </summary>
        /// <param name="lpData">Preparsed data reference</param>
        /// <param name="oCaps">HID capabilities structure to store the capabilities</param>
        /// <returns>True for success</returns>
        [DllImport("hid.dll", SetLastError = true)]
        protected static extern int HidP_GetCaps(IntPtr lpData, out HidCaps oCaps);

        /// <summary>
        /// Retrieves details from open device - reserves memory which must be deallocated
        /// </summary>
        /// <param name="hFile">Handle to the device file</param>
        /// <param name="lpData">Reference to the data block</param>
        /// <returns></returns>
        [DllImport("hid.dll", SetLastError = true)]
        protected static extern bool HidD_GetPreparsedData(IntPtr hFile, out IntPtr lpData);

        /// <summary>
        /// Deallocates the reserved memory from HidD_GetPreparsedData call
        /// </summary>
        /// <param name="pData">Reference to the data</param>
        /// <returns></returns>
        [DllImport("hid.dll", SetLastError = true)]
        protected static extern bool HidD_FreePreparsedData(ref IntPtr pData);

        /// <summary>
        /// Allocates a block of InfoSet memory which contains device details
        /// </summary>
        /// <param name="gClass">Class GUID</param>
        /// <param name="strEnumerator">Unused</param>
        /// <param name="hParent">Unused</param>
        /// <param name="nFlags">Device detail type - DIGCF_ constants</param>
        /// <returns>Reference to the InfoSet memory block</returns>
        [DllImport("setupapi.dll", SetLastError = true)]
        protected static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, [MarshalAs(UnmanagedType.LPStr)] string strEnumerator, IntPtr hParent, uint nFlags);
        
        /// <summary>
        /// Deallocates InfoSet memory allocated by SetupDiGetClassDevs
        /// </summary>
        /// <param name="lpInfoSet">Reference to InfoSet memory block</param>
        /// <returns>True for success</returns>
        [DllImport("setupapi.dll", SetLastError = true)]
        protected static extern int SetupDiDestroyDeviceInfoList(IntPtr lpInfoSet);

        /// <summary>
        /// Retrieves DeviceInterfaceData for a device from the InfoSet memory block
        /// </summary>
        /// <param name="lpDeviceInfoSet">InfoSet memory block</param>
        /// <param name="nDeviceInfoData">Unused</param>
        /// <param name="gClass">Class GUID</param>
        /// <param name="nIndex">Index within InfoSet of device</param>
        /// <param name="oInterfaceData">DeviceInterfaceData struct to store data in</param>
        /// <returns>True for success, false otherwise</returns>
        [DllImport("setupapi.dll", SetLastError = true)]
        protected static extern bool SetupDiEnumDeviceInterfaces(IntPtr lpDeviceInfoSet, uint nDeviceInfoData, ref Guid gClass, uint nIndex, ref DeviceInterfaceData oInterfaceData);

        /// <summary>
        /// Retrieves interface detail from the DeviceInterfaceData, i.e. the device path.
        /// Called twice to retrieve the size of the struct needed to store the data and again with memory allocated for data of that size.
        /// </summary>
        /// <param name="lpDeviceInfoSet">InfoSet memory block to access</param>
        /// <param name="oInterfaceData">DeviceInterfaceData to use</param>
        /// <param name="lpDeviceInterfaceDetailData">DeviceInterfaceDetailData struct to store data</param>
        /// <param name="nDeviceInterfaceDetailDataSize">The size of the DeviceInterfaceDetailData struct</param>
        /// <param name="nRequiredSize">Required size of DeviceInterfaceDetailData when initial size is zero</param>
        /// <param name="lpDeviceInfoData">Unused</param>
        /// <returns></returns>
        [DllImport("setupapi.dll", SetLastError = true)]
        protected static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr lpDeviceInfoSet, ref DeviceInterfaceData oInterfaceData, IntPtr lpDeviceInterfaceDetailData, uint nDeviceInterfaceDetailDataSize, ref uint nRequiredSize, IntPtr lpDeviceInfoData);
        [DllImport("setupapi.dll", SetLastError = true)]
        protected static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr lpDeviceInfoSet, ref DeviceInterfaceData oInterfaceData, ref DeviceInterfaceDetailData oDetailData, uint nDeviceInterfaceDetailDataSize, ref uint nRequiredSize, IntPtr lpDeviceInfoData);

        /// <summary>
        /// Registers a window for Windows device insertion/removal event messages
        /// </summary>
        /// <param name="hwnd">The handle of the window which receives messages</param>
        /// <param name="oInterface">DeviceBroadcastInterface struct</param>
        /// <param name="nFlags">DEVICE_NOTIFY_WINDOW_HANDLE</param>
        /// <returns>A handle used for unregistering</returns>
        [DllImport("user32.dll", SetLastError = true)]
        protected static extern IntPtr RegisterDeviceNotification(IntPtr hwnd, DeviceBroadcastInterface oInterface, uint nFlags);

        /// <summary>
        /// Unregister a window from Windows device insertion/removal event messages
        /// </summary>
        /// <param name="hHandle">Handle returned from registering</param>
        /// <returns>True for success</returns>
        [DllImport("user32.dll", SetLastError = true)]
        protected static extern bool UnregisterDeviceNotification(IntPtr hHandle);

        /// <summary>
        /// Creates and opens a file, or USB device in our case
        /// </summary>
        /// <param name="strName">Path to the device</param>
        /// <param name="nAccess">Access mode</param>
        /// <param name="nShareMode">Share mode</param>
        /// <param name="lpSecurity">Security details</param>
        /// <param name="nCreationFlags">Specifies between creating or opening</param>
        /// <param name="nAttributes">Other attributes</param>
        /// <param name="lpTemplate">Unused</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        protected static extern IntPtr CreateFile([MarshalAs(UnmanagedType.LPStr)] string strName, uint nAccess, uint nShareMode, IntPtr lpSecurity, uint nCreationFlags, uint nAttributes, IntPtr lpTemplate);

        /// <summary>
        /// Closes window, file, event handles
        /// </summary>
        /// <param name="hFile">Handle to close</param>
        /// <returns>True for success</returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        protected static extern int CloseHandle(IntPtr hFile);
        #endregion

        #region Public Functions
        /// <summary>
        /// Registers a window to receive device insertion/removal events
        /// </summary>
        /// <param name="hWnd">Handle to the window receiving events</param>
        /// <param name="gClass">Class of device to receive events about</param>
        /// <returns>A handle used for unregistering</returns>
        public static IntPtr RegisterForUsbEvents(IntPtr hWnd, Guid gClass)
        {
            DeviceBroadcastInterface oInterfaceIn = new DeviceBroadcastInterface();
            oInterfaceIn.Size = Marshal.SizeOf(oInterfaceIn);
            oInterfaceIn.ClassGuid = gClass;
            oInterfaceIn.DeviceType = DEVTYP_DEVICEINTERFACE;
            oInterfaceIn.Reserved = 0;
            return RegisterDeviceNotification(hWnd, oInterfaceIn, DEVICE_NOTIFY_WINDOW_HANDLE);
        }

        /// <summary>
        /// Unregisters a window from receiving device insertion/removal events
        /// </summary>
        /// <param name="hHandle">Handle returned from registerng</param>
        /// <returns>True for success</returns>
        public static bool UnregisterForUsbEvents(IntPtr hHandle)
        {
            return UnregisterDeviceNotification(hHandle);
        }

        /// <summary>
        /// Helper to retrieve the GUID
        /// </summary>
        public static Guid HIDGuid
        {
            get
            {
                Guid gHid;
                HidD_GetHidGuid(out gHid);
                return gHid;
            }
        }
    }
        #endregion
}
